package com.zs.letcode.hash_table;

import java.util.HashMap;

/**
 * 有效的数独
 * 请你判断一个9x9 的数独是否有效。只需要 根据以下规则 ，验证已经填入的数字是否有效即可。
 * <p>
 * 数字1-9在每一行只能出现一次。
 * 数字1-9在每一列只能出现一次。
 * 数字1-9在每一个以粗实线分隔的3x3宫内只能出现一次。（请参考示例图）
 * 数独部分空格内已填入了数字，空白格用'.'表示。
 * <p>
 * 注意：
 * <p>
 * 一个有效的数独（部分已被填充）不一定是可解的。
 * 只需要根据以上规则，验证已经填入的数字是否有效即可。
 * <p>
 * <p>
 * 示例 1：
 * <p>
 * <p>
 * 输入：board =
 * [["5","3",".",".","7",".",".",".","."]
 * ,["6",".",".","1","9","5",".",".","."]
 * ,[".","9","8",".",".",".",".","6","."]
 * ,["8",".",".",".","6",".",".",".","3"]
 * ,["4",".",".","8",".","3",".",".","1"]
 * ,["7",".",".",".","2",".",".",".","6"]
 * ,[".","6",".",".",".",".","2","8","."]
 * ,[".",".",".","4","1","9",".",".","5"]
 * ,[".",".",".",".","8",".",".","7","9"]]
 * 输出：true
 * 示例 2：
 * <p>
 * 输入：board =
 * [["8","3",".",".","7",".",".",".","."]
 * ,["6",".",".","1","9","5",".",".","."]
 * ,[".","9","8",".",".",".",".","6","."]
 * ,["8",".",".",".","6",".",".",".","3"]
 * ,["4",".",".","8",".","3",".",".","1"]
 * ,["7",".",".",".","2",".",".",".","6"]
 * ,[".","6",".",".",".",".","2","8","."]
 * ,[".",".",".","4","1","9",".",".","5"]
 * ,[".",".",".",".","8",".",".","7","9"]]
 * 输出：false
 * 解释：除了第一行的第一个数字从 5 改为 8 以外，空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
 * <p>
 * <p>
 * 提示：
 * <p>
 * board.length == 9
 * board[i].length == 9
 * board[i][j] 是一位数字或者 '.'
 * 相关标签
 * 数组
 * 哈希表
 * 矩阵
 * <p>
 * 作者：力扣 (LeetCode)
 * 链接：https://leetcode-cn.com/leetbook/read/hash-table/xxpit5/
 * 来源：力扣（LeetCode）
 * 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
 *
 * @author madison
 * @description
 * @date 2021/7/2 09:19
 */
public class Chapter14 {
    public static void main(String[] args) {

    }

    private static class Solution {
        /**
         * 方法：一次迭代
         *
         * @param board
         * @return
         */
        public boolean isValidSudoku(char[][] board) {
            int length = board.length;
            //二维数组line表示的是对应的行中是否有对应的数字，比如line[0][3]
            //表示的是第0行（实际上是第1行，因为数组的下标是从0开始的）是否有数字3
            int line[][] = new int[length][length];
            int column[][] = new int[length][length];
            int cell[][] = new int[length][length];
            for (int i = 0; i < length; i++) {
                for (int j = 0; j < length; j++) {
                    //如果还没有填数字，直接跳过
                    if (board[i][j] == '.') {
                        continue;
                    }
                    //num是当前格子的数字
                    int num = board[i][j] - '0' - 1;
                    //k是第几个单元格，9宫格数独横着和竖着都是3个单元格
                    int k = i / 3 * 3 + j / 3;
                    //如果当前数字对应的行和列以及单元格，只要一个由数字，说明冲突了，直接返回false。
                    //举个例子，如果line[i][num]不等于0，说明第i（i从0开始）行有num这个数字。
                    if (line[i][num] != 0 || column[j][num] != 0 || cell[k][num] != 0) {
                        return false;
                    }
                    //表示第i行有num这个数字，第j列有num这个数字，对应的单元格内也有num这个数字
                    line[i][num] = column[j][num] = cell[k][num] = 1;
                }
            }
            return true;
        }

        /**
         * 方法：一次迭代 哈希表
         *
         * @param board
         * @return
         */
        public boolean isValidSudoku1(char[][] board) {
            // init data
            HashMap<Integer, Integer>[] rows = new HashMap[9];
            HashMap<Integer, Integer>[] columns = new HashMap[9];
            HashMap<Integer, Integer>[] boxes = new HashMap[9];
            for (int i = 0; i < 9; i++) {
                rows[i] = new HashMap<>();
                columns[i] = new HashMap<>();
                boxes[i] = new HashMap<>();
            }

            // validate a board
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    char num = board[i][j];
                    if (num != '.') {
                        int n = (int) num;
                        int box_index = (i / 3) * 3 + j / 3;

                        // keep the current cell value
                        rows[i].put(n, rows[i].getOrDefault(n, 0) + 1);
                        columns[j].put(n, columns[j].getOrDefault(n, 0) + 1);
                        boxes[box_index].put(n, boxes[box_index].getOrDefault(n, 0) + 1);

                        // check if this value has been already seen before
                        if (rows[i].get(n) > 1 || columns[j].get(n) > 1 || boxes[box_index].get(n) > 1)
                            return false;
                    }
                }
            }
            return true;
        }

        /**
         * 2，位运算解决
         * 上面使用的是二维数组，实际上我们还可以使用一维数组，直接使用位运算来存储，因为在java中一个int类型是32位，用它来表示9个数字足够了。
         */
        public boolean isValidSudoku2(char[][] board) {
            int[] line = new int[9];
            int[] column = new int[9];
            int[] cell = new int[9];
            int shift = 0;
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    //如果还没有填数字，直接跳过
                    if (board[i][j] == '.')
                        continue;
                    shift = 1 << (board[i][j] - '0');
                    int k = (i / 3) * 3 + j / 3;
                    //如果对应的位置只要有一个大于0，说明有冲突，直接返回false
                    if ((column[i] & shift) > 0 || (line[i] & shift) > 0 || (cell[k] & shift) > 0) {
                        return false;
                    }
                    column[i] |= shift;
                    line[j] |= shift;
                    cell[k] |= shift;
                }
            }
            return true;
        }
    }
}
